/*
 **	Session.cpp
 **
 **	Published / author: 2006-03-29 / grymse@alhem.net
 **/
/*
Copyright (C) 2006 Anders Hedstrom

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifdef _WIN32
#pragma warning(disable:4786)
#endif

#include <afxdb.h>
#include <sql.h>

#include "Session.h"
#include "Database.h"
#include "Query.h"


#ifdef ODBCW_NAMESPACE
namespace ODBCW_NAMESPACE {
#endif

SQLHENV Session::m_henv = NULL;


Session::Session()
:m_hdbc(NULL)
,m_bBusy(false)
,m_nLastError(0)
{
}


Session::~Session()
{
	Disconnect();
	// ? ref count?
	if (m_henv)
	{
		SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
		m_henv = NULL;
	}
}


int Session::Connect(Database& db,const std::string& driverstring)
{
	SQLRETURN retcode;
	int value;

	if (!m_henv)
	{
		// Allocate environment handle (ENV)
		retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv);
		if (retcode == SQL_ERROR || retcode == SQL_SUCCESS_WITH_INFO)
		{
			ShowErrorInfo(retcode, SQL_HANDLE_ENV, m_henv);
		}
		if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
		{
			db.error("Allocate environment handle (ENV) failed");
			return -1;
		}

		// Set the ODBC version environment attribute
		retcode = SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
		if (retcode == SQL_ERROR || retcode == SQL_SUCCESS_WITH_INFO)
		{
			ShowErrorInfo(retcode, SQL_HANDLE_ENV, m_henv);
		}
		if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
		{
			db.error("Set the ODBC version environment attribute failed");
			return -2;
		}
	}

	// Allocate connection handle (DBC)
	retcode = SQLAllocHandle(SQL_HANDLE_DBC, m_henv, &m_hdbc);
	if (retcode == SQL_ERROR || retcode == SQL_SUCCESS_WITH_INFO)
	{
		ShowErrorInfo(retcode, SQL_HANDLE_DBC, m_hdbc);
	}
	if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
	{
		db.error("Allocate connection handle (DBC) failed");
		return -3;
	}

	// Set login timeout to 5 seconds.
	value = 5;
	SQLSetConnectAttr(m_hdbc, SQL_LOGIN_TIMEOUT, (SQLPOINTER)&value, 0);
	if (retcode == SQL_ERROR || retcode == SQL_SUCCESS_WITH_INFO)
	{
		ShowErrorInfo(retcode, SQL_HANDLE_DBC, m_hdbc);
	}

	// Connect to data source
	{
		unsigned char slask[1024];
		SQLSMALLINT cbl = 0;

		*slask = 0;
		retcode = SQLDriverConnect(m_hdbc, NULL,
			(unsigned char *)driverstring.c_str(), (SQLSMALLINT)driverstring.size(),
			slask, 1024, &cbl, SQL_DRIVER_PROMPT);

		if (cbl >= 0 && cbl < 1024)
		{
			slask[cbl] = 0;
		}
//		MessageBox(NULL, (const char *)slask, "SQLDriverConnect std::string", MB_OK);

		if (retcode == SQL_ERROR || retcode == SQL_SUCCESS_WITH_INFO)
		{
			ShowErrorInfo(retcode, SQL_HANDLE_DBC, m_hdbc);
		}
		if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
		{
			db.error("Connect to data source failed");
			db.error(driverstring);
			return -4;
		}
	}

	// TODO: SQLGetInfo - to get info about odbc driver capabilities

	return 0;
}


int Session::Connect(Database& db,const std::string& dsn,const std::string& user,const std::string& password)
{
	SQLRETURN retcode;
	int value;

	if (!m_henv)
	{
		// Allocate environment handle (ENV)
		retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv);
		if (retcode == SQL_ERROR || retcode == SQL_SUCCESS_WITH_INFO)
		{
			ShowErrorInfo(retcode, SQL_HANDLE_ENV, m_henv);
		}
		if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
		{
			db.error("Allocate environment handle (ENV) failed");
			return -1;
		}

		// Set the ODBC version environment attribute
		retcode = SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
		if (retcode == SQL_ERROR || retcode == SQL_SUCCESS_WITH_INFO)
		{
			ShowErrorInfo(retcode, SQL_HANDLE_ENV, m_henv);
		}
		if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
		{
			db.error("Set the ODBC version environment attribute failed");
			return -2;
		}
	}

	// Allocate connection handle (DBC)
	retcode = SQLAllocHandle(SQL_HANDLE_DBC, m_henv, &m_hdbc);
	if (retcode == SQL_ERROR || retcode == SQL_SUCCESS_WITH_INFO)
	{
		ShowErrorInfo(retcode, SQL_HANDLE_DBC, m_hdbc);
	}
	if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
	{
		db.error("Allocate connection handle (DBC) failed");
		return -3;
	}

	// Set login timeout to 5 seconds.
	value = 5;
	SQLSetConnectAttr(m_hdbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER)&value, 0);
	if (retcode == SQL_ERROR || retcode == SQL_SUCCESS_WITH_INFO)
	{
		ShowErrorInfo(retcode, SQL_HANDLE_DBC, m_hdbc);
	}

	// Connect to data source
	retcode = SQLConnect(m_hdbc,
							(SQLCHAR*) dsn.c_str(), SQL_NTS,
							(SQLCHAR*) user.c_str(), SQL_NTS,
							(SQLCHAR*) password.c_str(), SQL_NTS);

	if (retcode == SQL_ERROR || retcode == SQL_SUCCESS_WITH_INFO)
	{
		ShowErrorInfo(retcode, SQL_HANDLE_DBC, m_hdbc);
	}
	if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
	{
		db.error("Connect to data source failed");
		db.error(dsn);
		return -4;
	}

	// TODO: SQLGetInfo - to get info about odbc driver capabilities

	return 0;
}


void Session::Disconnect()
{
	if (m_hdbc)
	{
		SQLDisconnect(m_hdbc);
		SQLFreeHandle(SQL_HANDLE_DBC, m_hdbc);
		m_hdbc = NULL;
	}
}


void *Session::GetHandle()
{
	return m_hdbc;
}


void Session::SetBusy(bool b)
{
	m_bBusy = b;
}


bool Session::IsBusy()
{
	return m_bBusy;
}

int Session::GetLastError(std::string& strSqlState, std::string& strErrorMessage)
{
	strSqlState = m_strLastErrorSqlState;
	strErrorMessage = m_strLastErrorMessage;
	return m_nLastError;
}

void Session::ResetLastError()
{
	m_nLastError = 0;
	m_strLastErrorSqlState.empty();
	m_strLastErrorMessage.empty();
}

void Session::ShowErrorInfo(SQLRETURN rc, SQLSMALLINT hType, SQLHANDLE h)
{
	SQLRETURN retcode;
	SQLSMALLINT iRecord = 1;
	SQLCHAR szSqlState[REM_LEN];
	SQLINTEGER pfNativeError;
	SQLCHAR szErrorMsg[REM_LEN];
	SQLSMALLINT cbErrorMsgMax = REM_LEN - 1;
	SQLSMALLINT pcbErrorMsg;

/*
	printf("Handle type: %s\n", (hType == SQL_HANDLE_STMT) ? "Statement" :
		(hType == SQL_HANDLE_ENV) ? "Environment" :
		(hType == SQL_HANDLE_DBC) ? "DBC" : "???");*/

	retcode = SQLGetDiagRec (hType, h, // SQL_HANDLE_STMT, m_hstmt,
		iRecord, szSqlState, &pfNativeError,
		szErrorMsg, cbErrorMsgMax, &pcbErrorMsg);

/*
	printf(" *** %s *** sqlstate '%s'	errormsg '%s'\n",
		(rc == SQL_ERROR) ? "SQL_ERROR" :
		(rc == SQL_SUCCESS_WITH_INFO) ? "SQL_SUCCESS_WITH_INFO" : "",
		szSqlState, szErrorMsg);*/

	if (m_nLastError == 0)
	{
		m_nLastError = rc;
		m_strLastErrorSqlState = (char*)szSqlState;
		m_strLastErrorMessage = (char*)szErrorMsg;
	}

}


#ifdef ODBCW_NAMESPACE
} // namespace ODBCW_NAMESPACE {
#endif
